﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Collections.Generic;
using BlobTowerDefense.Game_Entities;
using BlobTowerDefense.Pages;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;

using Matrix = Microsoft.Xna.Framework.Matrix;


//Pokud bude potreba je moznost udelat tuhle tridu jako singleton

namespace BlobTowerDefense.Managers
{
    /// <summary>
    /// Managing all worlds in game
    /// </summary>
    public class WorldManager
    {
        #region Structs for storing info about worlds 

        // storing all needed data for loading new world
        private class loadInformation
        {
            public string ModelName { get; set; } // name of model file
            public string WorldName { get; set; } // name of world level
            public uint LevelID { get; set; } // ID of world
            public Matrix DefProj { get; set; } // default projection matrix
            public Matrix DefView { get; set; } // default view matrix
            public Matrix DefWorld { get; set; } // default world matrix
            public Vector3 SpawnPoint { get; set; } // set spawnpoint place for map

            public Vector3[] towers_positions;
            public Vector3[] towers_scales;

            public loadInformation(ContentManager content = null, string model = null, string world = null, uint number = 0, Vector3[] towers_positions = null, Vector3[] towers_scales = null)
            {
                ModelName = "Models/" + model;
                WorldName = world;
                LevelID = number;
                DefProj = Matrix.Identity;
                DefView = Matrix.Identity;
                DefWorld = Matrix.Identity;
                SpawnPoint = new Vector3(0, 0, 0);
                this.towers_positions = null;
                this.towers_scales = null;
            }

            public loadInformation(string model, string world, uint number, Matrix view)
            {
                ModelName = "Models/" + model;
                WorldName = world;
                LevelID = number;
                DefProj = Matrix.Identity;
                DefView = view;
                DefWorld = Matrix.Identity;
                SpawnPoint = new Vector3(0, 0, 0);
                this.towers_positions = null;
                this.towers_scales = null;
            }

            public loadInformation(string model, string world, uint number, Matrix view, Vector3[] towers_positions, Vector3[] towers_scales)
            {
                ModelName = "Models/" + model;
                WorldName = world;
                LevelID = number;
                DefProj = Matrix.Identity;
                DefView = view;
                DefWorld = Matrix.Identity;
                SpawnPoint = new Vector3(0, 0, 0);
                this.towers_positions = towers_positions;
                this.towers_scales = towers_scales;
            }

            public loadInformation(string modelName, string worldName, uint levelID, Matrix proj, Matrix view, Matrix world, Vector3 spawnPlace, Vector3[] towers_positions, Vector3[] towers_scales)
            {
                this.ModelName = "Models/" + modelName;
                this.WorldName = worldName;
                this.LevelID = levelID;
                this.DefProj = proj;
                this.DefView = view;
                this.DefWorld = world;
                this.SpawnPoint = spawnPlace;
                this.towers_positions = towers_positions;
                this.towers_scales = towers_scales;
            }
        }

        // class storing one active world
        private class activeWorld
        {
            public World World { get; set; }
            public uint ID { get; set; }
            public string Name { get; set; }
            public bool IsSet { get; set; }

            public activeWorld(World world, uint ID, string name, bool isSet)
            {
                World = world;
                this.ID = ID;
                Name = name;
                IsSet = isSet;
            }

            public activeWorld()
            {
                IsSet = false;
            }
        }

        #endregion        

        List<loadInformation> worldManager; // storing data witch is need for loading
        activeWorld active; // storing active world
        ContentManager content;
        GraphicsDevice device;


        /// <summary>
        /// Constructor witch have all data needed for loading new worlds
        /// </summary>
        public WorldManager(ContentManager content)
        {
            active = new activeWorld();

            this.content = content;
            this.device = GamePage.Device;

            worldManager = new List<loadInformation>();
        }

        #region Load information

        /// <summary>
        /// Add information needed to load new world
        /// </summary>
        /// <param name="modelName">Name of the model file</param>
        /// <param name="worldName">Name of the world (in game name)</param>
        /// <param name="levelID">ID of the level</param>
        /// <param name="proj">Default projection matrix</param>
        /// <param name="view">Default view matrix</param>
        /// <param name="world">Default world matrix</param>
        /// <param name="spawnPlace">Place to spawn enemies</param>
        /// <param name="buildingPositions">Position of towers</param>
        /// <param name="buildingScales">Scales of towers</param>
        public void AddLoadInfo(string modelName, string worldName, uint levelID, Matrix proj, Matrix view, Matrix world, 
            Vector3 spawnPlace, Vector3[] buildingPositions, Vector3[] buildingScales)
        {
            loadInformation info = new loadInformation(modelName, worldName, levelID, proj, view, world, spawnPlace, 
                buildingPositions, buildingScales);
            worldManager.Add(info);
        }

        /// <summary>
        /// Add information needed to load new world (you need to set matrices manualy after this),
        /// or it will be identity matrices
        /// </summary>
        /// <param name="modelName">Name of the model file</param>
        /// <param name="worldName">Name of the world (in game name)</param>
        /// <param name="levelID">ID of the level</param>
        public void AddLoadInfo(string modelName, string worldName, uint levelID)
        {
            loadInformation info = new loadInformation(modelName, worldName, levelID, Matrix.Identity);
            worldManager.Add(info);
        }

        /// <summary>
        /// Add information needed to load new world (you need to set matrices manualy after this),
        /// or it will be identity matrices
        /// </summary>
        /// <param name="modelName">Name of the model file</param>
        /// <param name="worldName">Name of the world (in game name)</param>
        /// <param name="levelID">ID of the level</param>
        /// <param name="view">View matrix for camera position</param>
        /// <param name="buildingPositions">Position of towers</param>
        /// <param name="buildingScales">Scales of towers</param>
        public void AddLoadInfo(string modelName, string worldName, uint levelID, Matrix view, Vector3[] buildingPositions, Vector3[] buildingScales)
        {
            loadInformation info = new loadInformation(modelName, worldName, levelID, view, buildingPositions, buildingScales);
            worldManager.Add(info);
        }

        /// <summary>
        /// Add information needed to load new world (you need to set projection and world matrices manualy),
        /// or it will be identity matrices
        /// </summary>
        /// <param name="modelName">Name of the model file</param>
        /// <param name="worldName">Name of the world (in game name)</param>
        /// <param name="levelID">ID of the level</param>
        /// <param name="view">View matrix for camera position</param>
        public void AddLoadInfo(string modelName, string worldName, uint levelID, Matrix view)
        {
            loadInformation info = new loadInformation(modelName, worldName, levelID, view);
            worldManager.Add(info);
        }

        /// <summary>
        /// Set default projection matrix to all saved worlds for load
        /// </summary>
        /// <param name="proj">Default projection matrix</param>
        public void SetProjToAll(Matrix proj)
        {
            foreach (loadInformation load in worldManager)
            {
                load.DefProj = proj;
            }
        }

        /// <summary>
        /// Set default world matrix to all saved worlds for load
        /// </summary>
        /// <param name="world">Default world matrix</param>
        public void SetWorldToAll(Matrix world)
        {
            foreach (loadInformation load in worldManager)
            {
                load.DefWorld = world;
            }
        }

        /// <summary>
        /// Set default view matrix to all saved worlds for load
        /// </summary>
        /// <param name="view">Deafult view matrix</param>
        public void SetViewToAll(Matrix view)
        {
            foreach (loadInformation load in worldManager)
            {
                load.DefView = view;
            }
        }

        /// <summary>
        /// Set default projection and world matrices to all saved worlds for load
        /// </summary>
        /// <param name="projection">Default projection matrix</param>
        /// <param name="world">Default world matrix</param>
        public void SetProjWorldToAll(Matrix projection, Matrix world)
        {
            foreach (loadInformation load in worldManager)
            {
                load.DefProj = projection;
                load.DefWorld = world;
            }
        }

        #endregion

        #region Get information about worlds

        /// <summary>
        /// Get name of the model by level ID
        /// </summary>
        /// <param name="levelID">ID of the level</param>
        /// <returns>Name of the model, if it is null no ID of this level exists</returns>
        public string getModelName(uint levelID)
        {
            loadInformation info = new loadInformation();

            if (getLevelInfo(null, levelID, ref info))
                return info.ModelName;

            return null;
        }

        /// <summary>
        /// Get level ID by name of the model
        /// </summary>
        /// <param name="modelName">Name of the model</param>
        /// <returns>Level ID, if it is -1 no model name of this name exists</returns>
        public int getLevelID(string modelName)
        {
            loadInformation info = new loadInformation();

            if (getLevelInfo(modelName, 0, ref info))
                return (int)info.LevelID;

            return -1;
        }

        /// <summary>
        /// Get name of the World, you must set 1 of the parameters
        /// </summary>
        /// <param name="modelName">You can search for it by the modelName</param>
        /// <param name="levelID">Or you can search for it by levelID</param>
        /// <returns>World name or null</returns>
        public string getWorldName(string modelName = null, int levelID = -1)
        {
            loadInformation info = new loadInformation();

            if (modelName == null && levelID < 0)
                return null;

            if (getLevelInfo(modelName, (uint)levelID, ref info))
                return info.WorldName;

            return null;
        }

        // get the right info from modelName or levelID and return it in infoRef
        // if any information is good return false
        // if you want look up for info with levelID set modelName to null
        private bool getLevelInfo(string modelName, uint levelID, ref loadInformation infoRef)
        {
            if (modelName == null)
            {
                foreach (loadInformation info in worldManager)
                {
                    if (info.LevelID == levelID)
                    {
                        infoRef = info;
                        return true;
                    }
                }
                return false;
            }
            else
            {
                foreach (loadInformation info in worldManager)
                {
                    if (info.ModelName == modelName)
                    {
                        infoRef = info;
                        return true;
                    }
                }
            }

            return false;
        }

        #endregion

        #region Active world

        /// <summary>
        /// Load new world and set it as active
        /// </summary>
        /// <param name="ID">ID of world</param>
        /// <returns>false if no world with this ID exists</returns>
        public bool LoadWorld(uint ID)
        {
            foreach (loadInformation info in worldManager)
            {
                if (info.LevelID == ID)
                {
                    return LoadWorld(info);
                }
            }
            return false;
        }

        /// <summary>
        /// Load new world and set it as active
        /// </summary>
        /// <param name="modelName">Name of model file wich will be loaded</param>
        /// <returns>false if this model file desnt exists</returns>
        public bool LoadWorld(string modelName)
        {
            foreach (loadInformation info in worldManager)
            {
                if (info.ModelName == modelName)
                {
                    return LoadWorld(info);
                }
            }
            return false;
        }

        /// <summary>
        /// Load new world and set them as active
        /// </summary>
        /// <param name="info">information which is needed to load a file</param>
        private bool LoadWorld(loadInformation info)
        {
            active.World = new World();

            active.Name = info.WorldName;
            active.ID = info.LevelID;

            try
            {
                active.World.LoadModel(info.ModelName);
            }
            catch (Microsoft.Xna.Framework.Content.ContentLoadException)
            {
                active.IsSet = false;
                return false;
            }

            try
            {
                active.World.TowerManager.LoadTowers(info.towers_positions, info.towers_scales);
            }
            catch (NullReferenceException)
            {
                active.IsSet = false;
                return false;
            }

            active.IsSet = true;

            setDefault(info);

            return true;
            
        }
        

        /// <summary>
        /// Set world as active
        /// Only one active world can be set
        /// </summary>
        /// <param name="name">Name of this world</param>
        /// <param name="levelID">ID of this world</param>
        /// <param name="world">World object</param>
        public void setActiveWorld(string name, uint levelID, World world)
        {
            active.Name = name;
            active.World = world;
            active.ID = levelID;
            active.IsSet = true;
        }

        /// <summary>
        /// Get name of active world
        /// </summary>
        /// <returns>World name or null if not set</returns>
        public string getActiveName()
        {
            if (active.IsSet)
                return active.Name;
            else
                return null;
        }

        /// <summary>
        /// Get ID of active world
        /// </summary>
        /// <returns>ID or -1 if not set</returns>
        public int getActiveID()
        {
            if (active.IsSet)
                return (int)active.ID;
            else
                return -1;
        }

        /// <summary>
        /// Get world
        /// </summary>
        /// <returns>Object of type World or null if not set</returns>
        public World getWorld()
        {
            if (active.IsSet)
                return active.World;
            else
                return null;
        }

        public bool DrawActiveWorld()
        {
            if (active.IsSet)
            {
                active.World.Draw();
                return true;
            }
            else
                return false;
        }

        /// <summary>
        /// Set default values to active world
        /// </summary>
        /// <param name="info">Structure with information about world</param>
        private void setDefault(loadInformation info)
        {
            active.World.SetViewMatrix(info.DefView); //this set default view matrix to CameraClass
        }

        #endregion

        public WAVETYPE? GetCurrentWaveType()
        {
            if ((this.active) == null || (this.active.World == null))
            {
                return null;
            }
            else
            {
                return this.active.World.GetCurrentWaveType();
            }
        }

        public bool LaunchNextWave()
        {
            return this.active.World.LaunchNextWave();
        }

        public bool WaveRunning()
        {
            return this.active.World.WaveRunning();
        }

        public bool WaveEnded()
        {
            return this.active.World.WaveEnded();
        }

        public void SpeciaAttack()
        {
            this.active.World.SpecialAttack();
        }

        public void UpgradeTowers()
        {
            this.active.World.UpgradeTowers();
        }
    }
}
